import express from "express";
import { exec } from "child_process";
import { promisify } from "util";
import path from "path";
import { promises as fs } from "fs";
import { extractProjectDirectory } from "../projects.js";

const router = express.Router();
const execAsync = promisify(exec);

// Helper function to get the actual project path from the encoded project name
async function getActualProjectPath(projectName) {
  try {
    return await extractProjectDirectory(projectName);
  } catch (error) {
    console.error(
      `Error extracting project directory for ${projectName}:`,
      error,
    );
    // Fallback to the old method
    return projectName.replace(/-/g, "/");
  }
}

// Helper function to validate git repository
async function validateGitRepository(projectPath) {
  try {
    // Check if directory exists
    await fs.access(projectPath);
  } catch {
    throw new Error(`Project path not found: ${projectPath}`);
  }

  try {
    // Use --show-toplevel to get the root of the git repository
    const { stdout: gitRoot } = await execAsync(
      "git rev-parse --show-toplevel",
      { cwd: projectPath },
    );
    const normalizedGitRoot = path.resolve(gitRoot.trim());
    const normalizedProjectPath = path.resolve(projectPath);

    // Ensure the git root matches our project path (prevent using parent git repos)
    if (normalizedGitRoot !== normalizedProjectPath) {
      throw new Error(
        `Project directory is not a git repository. This directory is inside a git repository at ${normalizedGitRoot}, but git operations should be run from the repository root.`,
      );
    }
  } catch (error) {
    if (error.message.includes("Project directory is not a git repository")) {
      throw error;
    }
    throw new Error(
      'Not a git repository. This directory does not contain a .git folder. Initialize a git repository with "git init" to use source control features.',
    );
  }
}

// Get git status for a project
router.get("/status", async (req, res) => {
  const { project } = req.query;

  if (!project) {
    return res.status(400).json({ error: "Project name is required" });
  }

  try {
    const projectPath = await getActualProjectPath(project);
    console.log("Git status for project:", project, "-> path:", projectPath);

    // Validate git repository
    await validateGitRepository(projectPath);

    // Get current branch
    const { stdout: branch } = await execAsync(
      "git rev-parse --abbrev-ref HEAD",
      { cwd: projectPath },
    );

    // Get git status
    const { stdout: statusOutput } = await execAsync("git status --porcelain", {
      cwd: projectPath,
    });

    const modified = [];
    const added = [];
    const deleted = [];
    const untracked = [];

    statusOutput.split("\n").forEach((line) => {
      if (!line.trim()) return;

      const status = line.substring(0, 2);
      const file = line.substring(3);

      if (status === "M " || status === " M" || status === "MM") {
        modified.push(file);
      } else if (status === "A " || status === "AM") {
        added.push(file);
      } else if (status === "D " || status === " D") {
        deleted.push(file);
      } else if (status === "??") {
        untracked.push(file);
      }
    });

    res.json({
      branch: branch.trim(),
      modified,
      added,
      deleted,
      untracked,
    });
  } catch (error) {
    console.error("Git status error:", error);
    res.json({
      error: error.message.includes("not a git repository") ||
          error.message.includes("Project directory is not a git repository")
        ? error.message
        : "Git operation failed",
      details: error.message.includes("not a git repository") ||
          error.message.includes("Project directory is not a git repository")
        ? error.message
        : `Failed to get git status: ${error.message}`,
    });
  }
});

// Get diff for a specific file
router.get("/diff", async (req, res) => {
  const { project, file } = req.query;

  if (!project || !file) {
    return res.status(400).json({
      error: "Project name and file path are required",
    });
  }

  try {
    const projectPath = await getActualProjectPath(project);

    // Validate git repository
    await validateGitRepository(projectPath);

    // Check if file is untracked
    const { stdout: statusOutput } = await execAsync(
      `git status --porcelain "${file}"`,
      { cwd: projectPath },
    );
    const isUntracked = statusOutput.startsWith("??");

    let diff;
    if (isUntracked) {
      // For untracked files, show the entire file content as additions
      const fileContent = await fs.readFile(
        path.join(projectPath, file),
        "utf-8",
      );
      const lines = fileContent.split("\n");
      diff = `--- /dev/null\n+++ b/${file}\n@@ -0,0 +1,${lines.length} @@\n` +
        lines.map((line) => `+${line}`).join("\n");
    } else {
      // Get diff for tracked files
      const { stdout } = await execAsync(`git diff HEAD -- "${file}"`, {
        cwd: projectPath,
      });
      diff = stdout || "";

      // If no unstaged changes, check for staged changes
      if (!diff) {
        const { stdout: stagedDiff } = await execAsync(
          `git diff --cached -- "${file}"`,
          { cwd: projectPath },
        );
        diff = stagedDiff;
      }
    }

    res.json({ diff });
  } catch (error) {
    console.error("Git diff error:", error);
    res.json({ error: error.message });
  }
});

// Commit changes
router.post("/commit", async (req, res) => {
  const { project, message, files } = req.body;

  if (!project || !message || !files || files.length === 0) {
    return res.status(400).json({
      error: "Project name, commit message, and files are required",
    });
  }

  try {
    const projectPath = await getActualProjectPath(project);

    // Validate git repository
    await validateGitRepository(projectPath);

    // Stage selected files
    for (const file of files) {
      await execAsync(`git add "${file}"`, { cwd: projectPath });
    }

    // Commit with message
    const { stdout } = await execAsync(
      `git commit -m "${message.replace(/"/g, '\\"')}"`,
      { cwd: projectPath },
    );

    res.json({ success: true, output: stdout });
  } catch (error) {
    console.error("Git commit error:", error);
    res.status(500).json({ error: error.message });
  }
});

// Get list of branches
router.get("/branches", async (req, res) => {
  const { project } = req.query;

  if (!project) {
    return res.status(400).json({ error: "Project name is required" });
  }

  try {
    const projectPath = await getActualProjectPath(project);
    console.log("Git branches for project:", project, "-> path:", projectPath);

    // Validate git repository
    await validateGitRepository(projectPath);

    // Get all branches
    const { stdout } = await execAsync("git branch -a", { cwd: projectPath });

    // Parse branches
    const branches = stdout
      .split("\n")
      .map((branch) => branch.trim())
      .filter((branch) => branch && !branch.includes("->")) // Remove empty lines and HEAD pointer
      .map((branch) => {
        // Remove asterisk from current branch
        if (branch.startsWith("* ")) {
          return branch.substring(2);
        }
        // Remove remotes/ prefix
        if (branch.startsWith("remotes/origin/")) {
          return branch.substring(15);
        }
        return branch;
      })
      .filter((branch, index, self) => self.indexOf(branch) === index); // Remove duplicates

    res.json({ branches });
  } catch (error) {
    console.error("Git branches error:", error);
    res.json({ error: error.message });
  }
});

// Checkout branch
router.post("/checkout", async (req, res) => {
  const { project, branch } = req.body;

  if (!project || !branch) {
    return res.status(400).json({
      error: "Project name and branch are required",
    });
  }

  try {
    const projectPath = await getActualProjectPath(project);

    // Checkout the branch
    const { stdout } = await execAsync(`git checkout "${branch}"`, {
      cwd: projectPath,
    });

    res.json({ success: true, output: stdout });
  } catch (error) {
    console.error("Git checkout error:", error);
    res.status(500).json({ error: error.message });
  }
});

// Create new branch
router.post("/create-branch", async (req, res) => {
  const { project, branch } = req.body;

  if (!project || !branch) {
    return res.status(400).json({
      error: "Project name and branch name are required",
    });
  }

  try {
    const projectPath = await getActualProjectPath(project);

    // Create and checkout new branch
    const { stdout } = await execAsync(`git checkout -b "${branch}"`, {
      cwd: projectPath,
    });

    res.json({ success: true, output: stdout });
  } catch (error) {
    console.error("Git create branch error:", error);
    res.status(500).json({ error: error.message });
  }
});

// Get recent commits
router.get("/commits", async (req, res) => {
  const { project, limit = 10 } = req.query;

  if (!project) {
    return res.status(400).json({ error: "Project name is required" });
  }

  try {
    const projectPath = await getActualProjectPath(project);

    // Get commit log with stats
    const { stdout } = await execAsync(
      `git log --pretty=format:'%H|%an|%ae|%ad|%s' --date=relative -n ${limit}`,
      { cwd: projectPath },
    );

    const commits = stdout
      .split("\n")
      .filter((line) => line.trim())
      .map((line) => {
        const [hash, author, email, date, ...messageParts] = line.split("|");
        return {
          hash,
          author,
          email,
          date,
          message: messageParts.join("|"),
        };
      });

    // Get stats for each commit
    for (const commit of commits) {
      try {
        const { stdout: stats } = await execAsync(
          `git show --stat --format='' ${commit.hash}`,
          { cwd: projectPath },
        );
        commit.stats = stats.trim().split("\n").pop(); // Get the summary line
      } catch (error) {
        commit.stats = "";
      }
    }

    res.json({ commits });
  } catch (error) {
    console.error("Git commits error:", error);
    res.json({ error: error.message });
  }
});

// Get diff for a specific commit
router.get("/commit-diff", async (req, res) => {
  const { project, commit } = req.query;

  if (!project || !commit) {
    return res.status(400).json({
      error: "Project name and commit hash are required",
    });
  }

  try {
    const projectPath = await getActualProjectPath(project);

    // Get diff for the commit
    const { stdout } = await execAsync(
      `git show ${commit}`,
      { cwd: projectPath },
    );

    res.json({ diff: stdout });
  } catch (error) {
    console.error("Git commit diff error:", error);
    res.json({ error: error.message });
  }
});

// Generate commit message based on staged changes
router.post("/generate-commit-message", async (req, res) => {
  const { project, files } = req.body;

  if (!project || !files || files.length === 0) {
    return res.status(400).json({
      error: "Project name and files are required",
    });
  }

  try {
    const projectPath = await getActualProjectPath(project);

    // Get diff for selected files
    let combinedDiff = "";
    for (const file of files) {
      try {
        const { stdout } = await execAsync(
          `git diff HEAD -- "${file}"`,
          { cwd: projectPath },
        );
        if (stdout) {
          combinedDiff += `\n--- ${file} ---\n${stdout}`;
        }
      } catch (error) {
        console.error(`Error getting diff for ${file}:`, error);
      }
    }

    // Use AI to generate commit message (simple implementation)
    // In a real implementation, you might want to use GPT or Claude API
    const message = generateSimpleCommitMessage(files, combinedDiff);

    res.json({ message });
  } catch (error) {
    console.error("Generate commit message error:", error);
    res.status(500).json({ error: error.message });
  }
});

// Simple commit message generator (can be replaced with AI)
function generateSimpleCommitMessage(files, diff) {
  const fileCount = files.length;
  const isMultipleFiles = fileCount > 1;

  // Analyze the diff to determine the type of change
  const additions = (diff.match(/^\+[^+]/gm) || []).length;
  const deletions = (diff.match(/^-[^-]/gm) || []).length;

  // Determine the primary action
  let action = "Update";
  if (additions > 0 && deletions === 0) {
    action = "Add";
  } else if (deletions > 0 && additions === 0) {
    action = "Remove";
  } else if (additions > deletions * 2) {
    action = "Enhance";
  } else if (deletions > additions * 2) {
    action = "Refactor";
  }

  // Generate message based on files
  if (isMultipleFiles) {
    const components = new Set(files.map((f) => {
      const parts = f.split("/");
      return parts[parts.length - 2] || parts[0];
    }));

    if (components.size === 1) {
      return `${action} ${[...components][0]} component`;
    } else {
      return `${action} multiple components`;
    }
  } else {
    const fileName = files[0].split("/").pop();
    const componentName = fileName.replace(/\.(jsx?|tsx?|css|scss)$/, "");
    return `${action} ${componentName}`;
  }
}

export default router;
